Understanding Android Data Persistence Options
Course: Android App Development
Topic: Understanding Android Data Persistence Options
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Official Website: JustAcademy
1. Introduction to Data Persistence in Android
Data persistence is the process of saving application data so that it remains available after an app is closed, an activity is recreated, or a device is restarted. Android applications frequently need to store user preferences, login settings, notes, product information, offline records, cached responses, and other data.
Without suitable persistence, information kept only in temporary memory may disappear when the application process ends. Android provides multiple storage options, and the correct choice depends on the type, size, sensitivity, structure, and lifetime of the data.
For example, a settings screen may store the selected theme, a notes app may save notes in a local database, and a shopping app may cache product information to support offline browsing.
2. Learning Objectives
- Understand the meaning and importance of data persistence.
- Identify Android's main data storage options.
- Compare DataStore, SharedPreferences, Room, files, and cloud storage.
- Choose an appropriate storage option for different application requirements.
- Save and retrieve simple preferences using Kotlin.
- Understand local databases, file storage, and offline-first applications.
- Apply security, performance, and data-management best practices.
3. What Is Data Persistence?
Data persistence means retaining information beyond the lifetime of a temporary variable or an individual screen. In Android, data may be stored locally on the device or remotely on a server.
| Storage Type |
Description |
Example |
| In-memory state |
Data held temporarily in RAM. |
A temporary animation value. |
| Saved UI state |
Small state values restored after certain activity or process recreation scenarios. |
Selected tab or current input text. |
| Preferences |
Stores small key-value settings. |
Theme selection or notification preference. |
| Local database |
Stores structured records and relationships. |
Notes, tasks, or inventory items. |
| File storage |
Stores documents, images, exports, or other file content. |
A downloaded PDF or an exported report. |
| Remote storage |
Stores data on a server accessed through a network. |
User profile synchronized across devices. |
4. Overview of Android Data Persistence Options
| Option |
Best For |
Key Consideration |
| DataStore |
Small preferences and typed settings. |
Asynchronous API; suitable for modern Kotlin applications. |
| SharedPreferences |
Existing apps and simple legacy key-value settings. |
Synchronous API can cause main-thread performance problems; DataStore is generally preferred for new preference storage. |
| Room |
Structured, queryable local data. |
Built on SQLite and provides compile-time query verification. |
| Internal app files |
Private files belonging to the app. |
Normally inaccessible to other apps without an explicit sharing mechanism. |
| App-specific external storage |
Larger app-owned files. |
Availability and removable storage behavior must be considered. |
| Shared storage |
User-accessible photos, videos, audio, and documents. |
Use appropriate APIs such as MediaStore or the Storage Access Framework. |
| Saved instance state |
Restoring small UI state after recreation. |
Not intended as permanent storage for important application records. |
| Cloud or backend storage |
Synchronization, shared data, and remote services. |
Requires network handling, authentication, and server-side security. |
5. DataStore for Small Settings and Preferences
DataStore is a modern Android storage solution for small amounts of data. It provides asynchronous data access through Kotlin coroutines and Flow. It is suitable for values such as a theme preference, onboarding completion status, or notification settings.
Two common approaches are:
- Preferences DataStore: stores key-value pairs without requiring a predefined schema.
- Proto DataStore: stores strongly typed objects using a defined Protocol Buffers schema.
5.1 Example: Saving a Theme Preference
The following example demonstrates Preferences DataStore. Ensure that your project includes a compatible DataStore Preferences dependency and the required Kotlin coroutine dependencies.
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private val Context.dataStore by preferencesDataStore(
name = "app_settings"
)
class SettingsRepository(private val context: Context) {
private val themeKey = stringPreferencesKey("theme")
val theme: Flow<String> = context.dataStore.data.map { preferences ->
preferences[themeKey] ?: "system"
}
suspend fun saveTheme(theme: String) {
context.dataStore.edit { preferences ->
preferences[themeKey] = theme
}
}
}
5.2 Explanation
preferencesDataStore creates a DataStore instance associated with the application context.
stringPreferencesKey defines a key for the stored theme value.
- The
theme Flow emits the current value and subsequent updates.
- The default value is
"system" when no preference has been saved.
saveTheme() updates the value using the asynchronous DataStore API.
- Call the suspend function from an appropriate coroutine scope, such as a ViewModel.
Use a repository to keep storage operations separate from UI code. Validate allowed values when necessary, and avoid storing passwords or other sensitive credentials as plain preferences.
6. SharedPreferences for Legacy Applications
SharedPreferences is a traditional Android API for storing small key-value pairs, including Boolean values, strings, integers, and other supported primitive types. It is useful when maintaining existing applications, although DataStore is generally the preferred option for new preference storage.
6.1 Saving and Reading Preferences
val preferences = getSharedPreferences(
"user_settings",
MODE_PRIVATE
)
// Save a preference
preferences.edit()
.putBoolean("notifications_enabled", true)
.apply()
// Read a preference
val notificationsEnabled = preferences.getBoolean(
"notifications_enabled",
false
)
6.2 Important Methods
| Method |
Purpose |
getSharedPreferences() |
Opens a named preferences file. |
getString() |
Reads a stored string. |
getBoolean() |
Reads a Boolean value. |
edit() |
Creates an editor for modifying preferences. |
putInt() |
Stores an integer value. |
remove() |
Removes a selected key. |
clear() |
Removes all values from that preferences file when committed or applied. |
apply() |
Schedules preference updates asynchronously. |
commit() |
Writes changes synchronously and returns a success result; avoid blocking the main thread. |
Important: SharedPreferences is not a secure credential vault. Do not store passwords, access tokens, or other secrets as unencrypted values. Also, avoid using it for large datasets or relational information.
7. Room Database for Structured Data
Room is a persistence library built on top of SQLite. It simplifies local database operations by providing entities, data access objects (DAOs), and database configuration. Room is useful when the application needs structured records, filtering, sorting, relationships, or updates to individual records.
7.1 Main Components of Room
| Component |
Description |
| Entity |
Defines a database table and its columns. |
| DAO |
Defines methods for inserting, reading, updating, and deleting data. |
| RoomDatabase |
Provides access to the database and its DAOs. |
| Repository |
Provides a clean data-access interface for the rest of the app. |
| ViewModel |
Coordinates UI-related state and requests data through the repository. |
7.2 Example: Creating an Entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String
)
The @Entity annotation defines a table named notes. The primary key uniquely identifies each note, while the title and content fields represent the note's stored information.
7.3 Creating a DAO
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface NoteDao {
@Insert
suspend fun insertNote(note: Note)
@Query("SELECT * FROM notes ORDER BY id DESC")
fun getAllNotes(): Flow<List<Note>>
@Query("DELETE FROM notes WHERE id = :noteId")
suspend fun deleteNote(noteId: Int)
}
The DAO defines the operations used to access the database. The query returns a Flow so a UI layer can observe changes over time. Inserts and deletes are suspend functions, allowing them to be called from a coroutine rather than blocking the main thread.
7.4 Creating the Database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [Note::class],
version = 1,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"notes_database"
).build().also { INSTANCE = it }
}
}
}
}
This example creates a single database instance for the application process. In a production project, configure Room dependencies, schema export, and database migrations appropriately. A migration should preserve existing user data when the database schema changes.
8. File-Based Data Persistence
File storage is appropriate when an application needs to save content that is not naturally represented as rows in a database. Examples include JSON documents, exported reports, downloaded files, images, and generated documents.
8.1 Internal App Storage
Internal app storage is suitable for files that belong to the app and should remain private to it. Files saved in the app's internal files directory normally do not require storage permissions. They are removed when the app is uninstalled unless they are separately backed up or synchronized.
fun saveTextFile(context: Context, content: String) {
context.openFileOutput(
"user_notes.txt",
Context.MODE_PRIVATE
).use { output ->
output.write(content.toByteArray(Charsets.UTF_8))
}
}
fun readTextFile(context: Context): String {
return context.openFileInput("user_notes.txt")
.bufferedReader(Charsets.UTF_8)
.use { reader -> reader.readText() }
}
8.2 Explanation
openFileOutput() opens a file in the app's internal files directory.
MODE_PRIVATE creates or replaces the file with app-private access.
use automatically closes the stream after the operation.
openFileInput() opens the saved file for reading.
- For potentially large files, perform file operations off the main thread.
8.3 Cache Storage
Cache directories are intended for temporary data that the app can recreate or download again. Android may remove cached files when storage space is needed. Do not use cache as the only location for important user-created information.
9. Internal Storage vs External Storage
| Feature |
Internal App Storage |
App-Specific External Storage |
| Privacy |
Private to the app by default. |
App-specific directory with scoped access. |
| Permissions |
No storage permission required for ordinary internal files. |
Generally no storage permission is needed for app-specific directories on modern Android versions. |
| Availability |
Available as long as the device and app storage are available. |
May be unavailable if the associated external volume is removed or unmounted. |
| Typical use |
Private documents and essential app files. |
App-owned files that may need more space. |
| Uninstall behavior |
Files are normally removed. |
App-specific files are normally removed. |
Use the platform-provided directory methods instead of hard-coded filesystem paths. For files users should be able to access or share, consider shared storage APIs rather than placing them in private app directories.
10. Shared Storage and the Storage Access Framework
Shared storage is designed for content that users may want to access outside the app, such as photos, videos, audio, and documents. Modern Android versions apply scoped-storage rules to protect user privacy.
- MediaStore: use for managing supported media content such as images, audio, and video.
- Storage Access Framework: lets users select documents or locations through a system-provided file picker.
- App-specific external directories: use for files belonging only to your app.
- Internal files directory: use for private files that should remain within the app's storage area.
Do not request broad storage permissions when a more specific API can complete the task. Permission requirements depend on the Android version, the target SDK, the file type, and whether the app is accessing its own content or content created by other apps.
11. Saved Instance State and ViewModel
Not every value needs permanent storage. Some data exists only to preserve the current screen experience. For example, a user may rotate the device while entering a search query or viewing a selected tab.
| Mechanism |
Purpose |
Limitation |
| remember |
Retains Compose state during recomposition. |
Does not by itself survive activity recreation or process death. |
| rememberSaveable |
Restores supported UI state through saved instance state. |
Not intended for large objects or permanent business data. |
| ViewModel |
Holds screen-related state across configuration changes. |
Does not by itself survive process death. |
| SavedStateHandle |
Allows a ViewModel to save and restore suitable small state values. |
Not a replacement for a database or durable file storage. |
| Room or DataStore |
Persists data to disk beyond temporary UI lifetimes. |
Requires appropriate storage and data-management design. |
For example, a search screen can keep its current query in saved state, while the user's saved searches can be stored in Room. Use the correct mechanism for the required lifetime of each piece of data.
12. Cloud Storage and Backend Synchronization
Cloud persistence stores information on a remote service rather than only on the device. It is useful when users need to access the same account or records from multiple devices, collaborate with others, or recover data after changing devices.
Common Approaches
- REST APIs: communicate with a backend server using HTTP requests and responses.
- Cloud databases: store structured or document-based data on a managed service.
- Object storage: store larger unstructured files such as images and documents.
- Synchronization: reconcile local changes with remote data.
Important Design Considerations
- Handle offline operation, timeouts, retries, and server errors.
- Define which source is authoritative when local and remote data differ.
- Use secure authentication and server-side authorization.
- Avoid embedding private server credentials or privileged API keys in the application.
- Consider conflicts when the same record is changed on multiple devices.
- Explain to users what is synchronized and what remains only on their device.
13. Choosing the Right Persistence Option
The correct storage solution depends on how the application uses the data, rather than on a single storage option being best for every situation.
| Requirement |
Recommended Starting Point |
Reason |
| Remember theme or display settings |
DataStore |
Designed for small preference data. |
| Maintain an existing SharedPreferences implementation |
SharedPreferences or a planned DataStore migration |
Avoid unnecessary rewrites while considering modern alternatives. |
| Save hundreds or thousands of searchable records |
Room |
Supports structured queries and database operations. |
| Save a private text or JSON file |
Internal app storage |
Suitable for app-owned file content. |
| Let the user export or select a document |
Storage Access Framework |
Uses the system file picker. |
| Save a photo to a media collection |
MediaStore |
Supports media-oriented shared storage. |
| Restore a small UI selection after recreation |
rememberSaveable or SavedStateHandle |
Designed for appropriate transient UI state. |
| Access records from several devices |
Backend or cloud storage |
Supports remote access and synchronization. |
14. Repository Pattern and Data Layer
The repository pattern separates the source of data from the UI. A screen should not need to know the details of how records are read from a Room database, DataStore, or a remote API.
A common architecture looks like this:
Compose UI
|
v
ViewModel
|
v
Repository
|
+---- Room Database
|
+---- DataStore
|
+---- Remote API
The repository can expose a clear interface for reading and changing data. This approach improves testability, makes it easier to replace data sources, and reduces the amount of storage-specific code inside UI components.
15. Data Security and Privacy
Persisted information may contain personal or business data, so storage decisions must consider confidentiality, integrity, and access control.
- Store private app files in internal storage where appropriate.
- Do not store passwords, private keys, or access tokens as plain text preferences or ordinary files.
- Use platform-supported secure credential storage approaches where appropriate.
- Use encryption when the threat model and data sensitivity require it.
- Use HTTPS for network communication and validate server authorization.
- Request only the permissions needed for the selected use case.
- Delete data when it is no longer required and respect user deletion requests.
- Never treat a hidden UI element as an authorization or security control.
- Consider backups and device transfer when handling sensitive information.
16. Data Migration, Backup, and Recovery
Applications evolve, and their data formats may change. A database migration updates the schema while preserving existing records. For Room databases, define version changes and migrations carefully, and test them using representative older database versions.
Important Practices
- Increase the database version when the Room schema changes.
- Write and test migrations for supported upgrade paths.
- Avoid destructive migration strategies when user data must be preserved.
- Determine which information should be included in backup and device transfer.
- Handle corrupted, missing, or invalid files gracefully.
- Keep a recovery or re-download strategy for data that can be recreated.
- Do not assume that every local file will automatically be backed up.
17. Common Data Persistence Errors and Solutions
| Problem |
Possible Cause |
Solution |
| Preference resets after reopening |
Value was not saved, or a different key or file is used. |
Verify the key, storage name, and write operation. |
| Database query blocks the UI |
Database work is performed on the main thread. |
Use suspend DAO methods, Flow, and appropriate coroutine scopes. |
| Room migration fails |
Migration does not match the actual schema change. |
Review schema versions and test migration paths. |
| File cannot be found |
Incorrect directory or a hard-coded path was used. |
Use Android directory APIs and validate the file location. |
| File operation throws an exception |
Missing file, unavailable storage, or I/O failure. |
Handle exceptions and recover gracefully. |
| Shared file is inaccessible |
Wrong storage API or missing access to selected content. |
Use MediaStore or the Storage Access Framework as appropriate. |
| Data disappears after uninstall |
Data was stored in app-specific storage. |
Use backup or server synchronization if data must survive reinstallation. |
| Local and remote records differ |
Synchronization failed or edits conflict. |
Define synchronization and conflict-resolution rules. |
| Sensitive information is exposed |
Secrets were stored without appropriate protection. |
Review storage, access control, encryption, and credential-handling design. |
18. Practical Exercise: Save User Preferences
Objective: Create a settings screen that remembers a user's preferred theme and notification setting.
Requirements
- Create a settings repository backed by Preferences DataStore.
- Define keys for the selected theme and notification preference.
- Implement functions to save and observe those values.
- Expose the settings through a ViewModel or suitable state holder.
- Connect the stored values to the UI controls.
- Close and reopen the app to verify that preferences remain saved.
- Handle read and write failures appropriately.
Expected Result
The application should restore the selected settings when the user returns to the app. The UI should reflect the stored values rather than always reverting to hard-coded defaults.
19. Mini Project: Offline Notes Application
Build a notes application that saves user-created notes locally and allows users to retrieve them even when the device is offline.
Core Features
- Create, edit, view, and delete notes.
- Store notes using Room.
- Use a DAO for database queries and updates.
- Use a repository to separate the database from UI code.
- Use a ViewModel to manage screen state.
- Use DataStore to remember a theme preference.
- Show a useful empty state when no notes exist.
- Handle database errors without crashing the application.
- Optionally add remote synchronization through a backend.
Suggested Development Steps
- Design the note entity and database schema.
- Implement insert, query, update, and delete operations.
- Create a repository for note-related operations.
- Connect the repository to a ViewModel.
- Build the Compose screens for listing and editing notes.
- Persist the theme preference with DataStore.
- Test app restarts, empty data, editing, deletion, and offline use.
20. Interview Questions and Answers
Q1. What is data persistence in Android?
Answer: Data persistence is the process of storing application data so that it remains available beyond temporary memory or a single screen's lifetime.
Q2. What is the difference between DataStore and Room?
Answer: DataStore is suitable for small preference or typed settings data. Room is designed for structured records that need queries, relationships, and individual record updates.
Q3. What is SharedPreferences?
Answer: SharedPreferences is a traditional Android API for saving small key-value pairs. DataStore is generally preferred for new preference storage because it offers an asynchronous API.
Q4. What are the main components of Room?
Answer: The main components are entities, DAOs, and a RoomDatabase class. Repositories and ViewModels are commonly used to organize the broader application architecture.
Q5. When should internal storage be used?
Answer: Use internal storage for files that belong to the app and should not normally be accessible to other apps.
Q6. What is the Storage Access Framework?
Answer: It provides system file-picker interfaces that allow users to select documents and locations for reading or writing files.
Q7. Does ViewModel permanently save data?
Answer: No. A ViewModel can survive configuration changes, but it does not by itself guarantee persistence through process death or app restarts. Use durable storage when required.
Q8. What is a Room migration?
Answer: A migration updates the database schema between versions while preserving existing data when designed and implemented correctly.
Q9. Why should database operations not block the main thread?
Answer: Slow database operations on the main thread can make the interface unresponsive and may lead to application-not-responding errors.
Q10. How can an Android app synchronize local data with a server?
Answer: It can use a repository and network API to exchange changes with a backend, while handling connectivity, retries, authentication, and conflicting edits.
Q11. What happens to app-specific files after uninstalling an app?
Answer: App-specific files are normally removed during uninstall. Data that must survive should have an appropriate backup, export, or server synchronization strategy.
Q12. Can SharedPreferences be used to store passwords?
Answer: Plain SharedPreferences should not be used to store passwords or secrets. Use suitable secure credential-handling approaches and avoid retaining sensitive credentials unnecessarily.
21. Quick Revision Checklist
- Use DataStore for small settings and preference values.
- Understand SharedPreferences when maintaining legacy applications.
- Use Room for structured, queryable local data.
- Use internal storage for private app files.
- Use MediaStore or the Storage Access Framework for appropriate shared content.
- Use saved instance state for small UI restoration needs.
- Use backend storage when data must be shared or synchronized across devices.
- Keep database and file operations off the main thread when they may take time.
- Plan migrations, backup, error handling, and recovery.
- Protect sensitive information and request only necessary permissions.
22. Official Learning Resources
23. Conclusion
Understanding Android data persistence helps developers choose reliable ways to store settings, structured records, files, UI state, and remote data. DataStore is suitable for small preferences, Room supports structured local databases, file APIs handle documents and other file content, and backend services support synchronization across devices. Saved instance state and ViewModel serve different purposes from durable storage.
Choosing the right approach, protecting sensitive information, handling errors, and testing data recovery helps create Android applications that remain reliable beyond a single session.
Explore the Android App Development course, register for a course demo, or visit the JustAcademy website to learn more.